#!/usr/bin/env python
"""
Acorn-SDR (A Collaboration of Radio Nodes)
(c) Bob Cowdery (G3UKB) 2009

Date: January 2009

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

The author can be reached by email at

bob@g3ukb.co.uk

========================================================================
Module Description:

A GUI implementation using GTK+.

"""

#-----------------------------------------------------------------------
# System imports
import sys
from time import sleep
import subprocess
import string

# Import GTK libraries
try:
  import pygtk
  #tell pyGTK, if possible, that we want GTKv2
  pygtk.require("2.0")
except:
  #Some distributions come with GTK2, but not pyGTK
  pass
try:
  import gtk
  import gtk.glade
  import gobject
except:
  print "You need to install pyGTK or GTKv2 ",
  print "or set your PYTHONPATH correctly."
  print "try: export PYTHONPATH=",
  print "/usr/local/lib/python2.2/site-packages/"
  sys.exit(1)

# Application imports
import utils
import gtk_options
import gtk_modes
import gtk_filters
import gtk_agc
import gtk_rx_proc
import gtk_radio
import gtk_vfo

#======================================================================
# The GUI class. There will be other classes downstream.
class AppGui:
  
  #---------------------------------------------------------------------
  def __init__(self, w_pos, glade_path, persist_service, running, lc_pub, hw_pub, dsp_pub, upd_pub, console_path, display_path, ice_config, init):
    # Allow threads, without this the gui will hang all python threads
    # We don't actually use any Python threads right now but its very
    # disconcerting if you do and find they block, so leave this in
    gtk.gdk.threads_init()
    # This is how we send events into the system
    self.running = running
    self.persist = persist_service
    self.hw_pub = hw_pub
    self.dsp_pub = dsp_pub
    self.lc_pub = lc_pub
    self.upd_pub = upd_pub
    self.console_path = console_path
    self.display_path = display_path
    self.ice_config = ice_config
    self.vr_data = init
    # Initialise the glade built ui
    # Load the main window
    self.entered = False # Not in the window
    mainwindow = 'MainWindow'
    modeswindow = 'Modes'
    filterswindow = 'Filters'
    agcwindow = 'Agc'
    rxprocwindow = 'RxProc'
    radiowindow = 'Radios'
    # This loads a widget tree from the XML definition in the glade file
    self.main_tree=gtk.glade.XML (glade_path, mainwindow)
    self.modes_tree=gtk.glade.XML (glade_path, modeswindow)
    self.filters_tree=gtk.glade.XML (glade_path, filterswindow)
    self.agc_tree=gtk.glade.XML (glade_path, agcwindow)
    self.rx_proc_tree=gtk.glade.XML (glade_path, rxprocwindow)
    self.radio_tree=gtk.glade.XML (glade_path, radiowindow)
    
    # Get and show the main window
    self.w=self.main_tree.get_widget(mainwindow)
    self.w.show_all()
    # Position and size to last settings
    self.w.move(w_pos[0], w_pos[1])
    self.w.resize(w_pos[2], w_pos[3])
    
    # Start the options class
    self.options = gtk_options.OptionsGui(self.main_tree, self.persist)
  
    # Set to true if we are quiting
    self.quitall = False
    
    # Wire up the top level window events
    # All events are set up in Glade, just match the event names here
    dic = { "on_MainWindow_destroy" : self.destroy,
            "on_MainWindow_destroy_event" : self.delete_event,
            "on_MainWindow_enter_notify_event" : self.enter_notify,
            "on_MainWindow_configure_event" : self.configure,
            # Toolbar
            "on_tb_run_toggled" : self.run_toggled,
            "on_tb_close_clicked" : self.close,
            # VFO's etc
            "on_tb_vfoa_toggled" : self.vfoa_toggled,
            "on_tb_vfob_toggled" : self.vfob_toggled,
            "on_tb_offseta_toggled" : self.offseta_toggled,
            "on_tb_offsetb_toggled" : self.offsetb_toggled,
            "on_b_zero_offseta_clicked" : self.zero_offseta,
            "on_b_zero_offsetb_clicked" : self.zero_offsetb,
            "on_tb_c_split_toggled" : self.vfo_split,
            "on_b_c_atob_clicked" : self.vfo_atob,
            "on_b_c_aswpb_clicked" : self.vfo_aswpb,
            "on_b_c_btoa_clicked" : self.vfo_btoa,
            # Function controls
            "on_b_c_radio_sel_clicked" : self.vr_controls,
            "on_b_c_rx_mode_clicked" : self.mode_controls,
            "on_b_c_rx_filter_clicked" : self.rx_filter_controls,
            "on_b_c_rx_proc_sel_clicked" : self.rx_proc_controls,
            "on_b_c_rx_agc_clicked" : self.agc_controls,
            
            # Receivers
            "on_tb_c_run_toggled" : self.run_radio_toggled,
            
            # Pan for receivers
            "on_hs_s_pan_value_changed" : self.pan_changed,
            
            # Common af gain
            "on_hs_c_af_value_changed" : self.af_gain_changed,
            }
    
    # This will connect up all the events in one hit
    self.main_tree.signal_autoconnect (dic)
    
    # Now we need a reference to all the controls
    # All controls in the tree must have a unique name
    # Toolbar
    self.brun = self.main_tree.get_widget('tb_run')
    self.bclose = self.main_tree.get_widget('tb_close')
    # VFO's etc
    self.box_osc = self.main_tree.get_widget('hbox_osc')
    self.l_vfoa = self.main_tree.get_widget('l_vfoa')
    self.b_vfoa = self.main_tree.get_widget('tb_vfoa')
    self.l_vfob = self.main_tree.get_widget('l_vfob')
    self.b_vfob = self.main_tree.get_widget('tb_vfob')
    self.l_offseta = self.main_tree.get_widget('l_offseta')
    self.b_offseta = self.main_tree.get_widget('tb_offseta')
    self.b_zero_offseta = self.main_tree.get_widget('b_zero_offseta')
    self.l_offsetb = self.main_tree.get_widget('l_offsetb')
    self.b_offsetb = self.main_tree.get_widget('tb_offsetb')
    self.b_zero_offsetb = self.main_tree.get_widget('b_zero_offsetb')
    self.b_split = self.main_tree.get_widget('tb_c_split')
    self.b_atob = self.main_tree.get_widget('b_c_atob')
    self.b_aswpb = self.main_tree.get_widget('b_c_aswpb')
    self.b_btoa = self.main_tree.get_widget('b_c_btoa')
    # General controls
    self.l_radio_controls = self.main_tree.get_widget('l_c_radio_sel')
    self.b_radio_controls = self.main_tree.get_widget('b_c_radio_sel')
    self.pan = self.main_tree.get_widget('hs_s_pan')
    self.l_mode_controls = self.main_tree.get_widget('l_c_rx_mode')
    self.b_mode_controls = self.main_tree.get_widget('b_c_rx_mode')
    self.l_rx_filter_controls = self.main_tree.get_widget('l_c_rx_filter')
    self.b_rx_filter_controls = self.main_tree.get_widget('b_c_rx_filter')
    self.l_rx_proc_controls = self.main_tree.get_widget('l_c_rx_proc_sel')
    self.b_rx_proc_controls = self.main_tree.get_widget('b_c_rx_proc_sel')
    self.l_agc_controls = self.main_tree.get_widget('l_c_rx_agc')
    self.b_agc_controls = self.main_tree.get_widget('b_c_rx_agc')
    self.af_gain = self.main_tree.get_widget('hs_c_af')
    # Virtual radio related
    self.b_radio_run = self.main_tree.get_widget('tb_c_run')
    self.l_vr_1 = self.main_tree.get_widget('l_c_radio_1')
    self.l_vr_2 = self.main_tree.get_widget('l_c_radio_2')
    self.l_vr_3 = self.main_tree.get_widget('l_c_radio_3')
    # Set the status bar
    self.sbmain = self.main_tree.get_widget('sb_main')
    self.context_id = self.sbmain.get_context_id("MainContext")
    profile = persist_service.getProfile()
    self.sbmain.push(self.context_id, profile)
    # Disable main radio
    self.mainbox = self.main_tree.get_widget('vbox_radio')
    self.mainbox.set_sensitive(False)

    # Default to main radio and disable the run (uses main run button)
    self.which_radio = 0
    self.b_radio_run .set_sensitive(False)
    self.l_radio_controls.set_label('<span font_desc=\"Times 12 \" foreground=\"red\">Main</span>')
    
    # Init the current freq
    self.freq = 0.0
    
    # Avoid circular events
    # One issue with messaging is that a lot of controls generate an event when they
    # are changed programmatically. So you hit a button, it sends a message, then a 
    # message comes back that causes the button state to change which causes it to 
    # generate an event ... 
    self.ignore = False
    
    # If we are a clone the system could already be running
    if self.running:
      self.ignore = True
      self.mainbox.set_sensitive(True)
      self.brun.set_active(True)
      self.ignore = False
    
    # Finally, we need to set our GUI according to capabilities
    cap_multirx = utils.persist_get(self.persist, 'Capability', 'multirx')
    self.cap_multirx = cap_multirx
    if not cap_multirx:
      self.l_radio_controls.set_label('<span font_desc=\"Times 12\" foreground=\"grey\">Main</span>')
      self.b_radio_controls.set_sensitive(False)
      self.pan.set_sensitive(False)
    cap_vfo = utils.persist_get(self.persist, 'Capability', 'vfo')
    if not cap_vfo:
      self.b_vfoa.set_sensitive(False)
      self.b_vfob.set_sensitive(False)
      self.b_split.set_sensitive(False)
      self.b_atob.set_sensitive(False)
      self.b_aswpb.set_sensitive(False)
      self.b_btoa.set_sensitive(False)
      # No vfo so must make sure we set the centre frequency and not assume the dynamic data to be correct
      # lowFreq and highFreq will both be set to the same
      cap_lowFreq = utils.persist_get(self.persist, 'Capability', 'lowFreq')
      self.vr_data[0]['vfoa'] = cap_lowFreq
      self.vr_data[0]['vfob'] = cap_lowFreq
    cap_oscillator = utils.persist_get(self.persist, 'Capability', 'oscillator')
    self.cap_oscillator = cap_oscillator
    if not cap_oscillator:
      self.b_offseta.set_sensitive(False)
      self.b_offsetb.set_sensitive(False)
      self.b_zero_offseta.set_sensitive(False)
      self.b_zero_offsetb.set_sensitive(False)
    cap_rxproc = utils.persist_get(self.persist, 'Capability', 'rxproc')
    if not cap_rxproc:
      self.l_rx_proc_controls.set_label('<span font_desc=\"Times 12\" foreground=\"grey\">---</span>')
      self.b_rx_proc_controls.set_sensitive(False)      
    cap_graphics = utils.persist_get(self.persist, 'Capability', 'graphics')
    if not cap_graphics:
      self.bdisplays.set_sensitive(False)
      
    # These are passed into the panels
    cap_modes = utils.persist_get(self.persist, 'Capability', 'modes')
    cap_filters = utils.persist_get(self.persist, 'Capability', 'filters')
    cap_agc = utils.persist_get(self.persist, 'Capability', 'agc')
    cap_rx_proc = utils.persist_get(self.persist, 'Capability', 'rxproc')
    
    # Create the popup windows
    self.c_modes = gtk_modes.ModesGui(self.modes_tree, cap_modes, self.dsp_pub, self.persist, self.mode_callback)
    self.c_filters = gtk_filters.FiltersGui(self.filters_tree, cap_filters, self.dsp_pub, self.persist, self.rx_filter_callback)
    self.c_agc = gtk_agc.AgcGui(self.agc_tree, cap_agc, self.dsp_pub, self.persist, self.agc_callback)
    self.c_rx_proc = gtk_rx_proc.RxProcGui(self.rx_proc_tree, cap_rx_proc, self.dsp_pub, self.persist, self.rx_proc_callback)
    self.c_radio = gtk_radio.RadioGui(self.radio_tree, None, self.dsp_pub, self.persist, self.vr_callback)
    
    # Create the VFO and Oscillator
    self.c_vfo = gtk_vfo.VfoGui(self.main_tree, 'on_evb', 'l_', 9, False, cap_vfo, self.persist)
    
    # Init controls that are not necessarily going to arrive as events up front or until state changes
    self.freq_update('vfoa', self.vr_data[0]['vfoa'])
    self.freq_update('vfob', self.vr_data[0]['vfob'])
    if cap_oscillator:
      self.osc_update('offseta', self.vr_data[0]['offseta'])
      self.osc_update('offsetb', self.vr_data[0]['offsetb'])
    self.mode_update('vfoa', self.vr_data[0]['modea'])
    self.mode_update('vfob', self.vr_data[0]['modeb'])
    self.filter_update('vfoa', self.vr_data[0]['filtera'])
    self.filter_update('vfob', self.vr_data[0]['filterb'])
    self.track = self.vr_data[0]['track']
    if (self.track == 'vfoa') or (self.track == 'offseta'):
      self.b_zero_offseta.set_sensitive(True)
      self.b_zero_offsetb.set_sensitive(False)
    else:
      self.b_zero_offseta.set_sensitive(False)
      self.b_zero_offsetb.set_sensitive(True)
    
    # Set the sub-radio run state
    if cap_multirx:
      if self.vr_data[1]['running']:
        self.l_vr_1.set_label('<span font_desc=\"Times 12\" foreground=\"red\">1</span>')
      if self.vr_data[2]['running']:
        self.l_vr_2.set_label('<span font_desc=\"Times 12\" foreground=\"red\">2</span>')
      if self.vr_data[3]['running']:
        self.l_vr_3.set_label('<span font_desc=\"Times 12\" foreground=\"red\">3</span>')
  
  #=====================================================================
  #---------------------------------------------------------------------
  # Update an option
  def update_option(self, name):
    gobject.idle_add(self.options.update, name)
    
  #---------------------------------------------------------------------
  # GUI generated events (i.e. things that happen when you play with widgets)
  
  #=====================================================================
  # Events for the main window
  def delete_event(self, widget, event, data=None):
    # If you return FALSE in the "delete_event" signal handler,
    # GTK will emit the "destroy" signal. Returning TRUE means
    # you don't want the window to be destroyed.
    # This is useful for popping up 'are you sure you want to quit?'
    # type dialogs.

    # Change FALSE to TRUE and the main window will not be destroyed
    # with a "delete_event".
    return False

  #---------------------------------------------------------------------
  def destroy(self, widget, data=None):
    pass
    #self.lc_pub.terminate()
  
  #---------------------------------------------------------------------
  def configure(self, widget, data=None):
    # Unfortunately its not possible to get accurate position info as
    # most window managers are broken in this respect.
    x, y = widget.get_position()
    width, height = widget.get_size()
    utils.persist_put(self.persist, 'Profile', 'window.x', x )
    utils.persist_put(self.persist, 'Profile', 'window.y', y )
    utils.persist_put(self.persist, 'Profile', 'window.width', width )
    utils.persist_put(self.persist, 'Profile', 'window.height', height )
    
  #---------------------------------------------------------------------
  def close(self, widget):
    # Just us to go then
    gtk.main_quit()
    
  #---------------------------------------------------------------------
  def run_toggled(self, widget):
    if not self.ignore:
      if widget.get_active():
        self.lc_pub.start()
      else:
        self.lc_pub.stop()
  
  #---------------------------------------------------------------------
  def enter_notify(self, widget, e):
    # The mouse has entered the top level window
    # Set to listen to the radio setting for this window.
    if self.cap_multirx:
      self.dsp_pub.setRxListen(self.which_radio)
    
  #=====================================================================
  # Receiver pan
  #---------------------------------------------------------------------  
  def pan_changed(self, widget):
    if not self.ignore:
      self.dsp_pub.setRxPan(int(widget.get_value()))
      utils.persist_put(self.persist, 'Dynamic', 'pan', int(widget.get_value()), self.which_radio)

  #=====================================================================
  # AF gain
  #---------------------------------------------------------------------  
  def af_gain_changed(self, widget):
    if not self.ignore:
      self.dsp_pub.setGain(int(widget.get_value()))
      utils.persist_put(self.persist, 'Dynamic', 'gain', int(widget.get_value()))

  #=====================================================================
  # VFO's etc
  #---------------------------------------------------------------------
  # This first set of callbacks are from the selection buttons. The effect of
  # clicking a vfo or offset button (which are only enabled on the main radio)
  # is -
  # 1. Firstly to set the persistent state for the new selection in the dynamic
  # part of the db to track vfoa,b or offseta,b.
  # 2. Secondly to fire an update command for the new track. The update will be
  # received by all listeners, which generally are GUI nodes including displays
  # but could be command line interfaces or special nodes like CAT etc.
  # NB the node that created the event may also receive it and this update is
  # where the real work of changing track is done.
  def vfoa_toggled(self, widget):
    if not self.ignore:
      self.upd_pub.trackUpdate('vfoa')
      self.b_zero_offseta.set_sensitive(True)
      self.b_zero_offsetb.set_sensitive(False)
      utils.persist_put(self.persist, 'Dynamic', 'track', 'vfoa')
  
  #---------------------------------------------------------------------  
  def vfob_toggled(self, widget):
    if not self.ignore:
      self.upd_pub.trackUpdate('vfob')
      self.b_zero_offseta.set_sensitive(False)
      self.b_zero_offsetb.set_sensitive(True)
      utils.persist_put(self.persist, 'Dynamic', 'track', 'vfob')
      
  #---------------------------------------------------------------------  
  def offseta_toggled(self, widget):
    if not self.ignore:
      self.upd_pub.trackUpdate('offseta')
      self.b_zero_offseta.set_sensitive(True)
      self.b_zero_offsetb.set_sensitive(False)
      utils.persist_put(self.persist, 'Dynamic', 'track', 'offseta')
      
  #---------------------------------------------------------------------  
  def offsetb_toggled(self, widget):
    if not self.ignore:
      self.upd_pub.trackUpdate('offsetb')
      self.b_zero_offseta.set_sensitive(False)
      self.b_zero_offsetb.set_sensitive(True)
      utils.persist_put(self.persist, 'Dynamic', 'track', 'offsetb')
      
  #---------------------------------------------------------------------  
  def zero_offseta(self, widget):
    if not self.ignore:
      self.dsp_pub.oscSet(0.0)
      
  #---------------------------------------------------------------------  
  def zero_offsetb(self, widget):
    if not self.ignore:
      self.dsp_pub.oscSet(0.0)
  
  # The second set simply move VFO's around.
  #---------------------------------------------------------------------  
  def vfo_split(self, widget):
    pass
  
  #---------------------------------------------------------------------  
  def vfo_atob(self, widget):
    freq = self.vr_data[self.which_radio]['vfoa']
    utils.persist_put(self.persist, 'Dynamic', 'vfob', freq)
    track = self.vr_data[0]['track']
    # We have changed the active frequency so event a freq change
    if track == 'vfob' or track == 'offsetb':
      self.hw_pub.freqSet(freq)
    else:
      self.freq_update('vfob', freq, True)
      
  #---------------------------------------------------------------------  
  def vfo_aswpb(self, widget):
    freq_a = self.vr_data[self.which_radio]['vfoa']
    freq_b = self.vr_data[self.which_radio]['vfob']
    utils.persist_put(self.persist, 'Dynamic', 'vfoa', freq_b)
    utils.persist_put(self.persist, 'Dynamic', 'vfob', freq_a)
    track = self.vr_data[0]['track']
    # We have changed the active frequency so event a freq change
    if track == 'vfoa' or track == 'offseta':
      self.hw_pub.freqSet(freq_b)
      self.freq_update('vfob', freq_a, True)
    else:
      self.hw_pub.freqSet(freq_a)
      self.freq_update('vfoa', freq_b, True)

  #---------------------------------------------------------------------  
  def vfo_btoa(self, widget):
    freq = self.vr_data[self.which_radio]['vfob']
    utils.persist_put(self.persist, 'Dynamic', 'vfoa', freq)
    track = self.vr_data[0]['track']
    # We have changed the active frequency so event a freq change
    if track == 'vfoa' or track == 'offseta':
      self.hw_pub.freqSet(freq)
    else:
      self.freq_update('vfoa', freq, True)
            
  #=====================================================================
  # Events for the function controllers
  #---------------------------------------------------------------------
  def vfo_controls(self, widget):
    if not self.ignore:
      pass
  
  #---------------------------------------------------------------------  
  def vr_controls(self, widget):
    if not self.ignore:
      # This selects the real or virtual radio for this instance
      # These are not broadcast events as each GUI can listen to a different vr.
      
      # Show the popup selection window
      # When done this will callback at vr_callback
      self.c_radio.show_window()
  
  # Callback from the vr_controls window on selection
  # We need to set the new offsets, pan, mode and filter
  def vr_callback(self, radio):
    # Dont bother if the radio didn't change
    if radio == self.which_radio: return
    
    # Otherwise set up for the new radio on this instance
    self.which_radio = radio
    
    # Modify controls valid for the radio.
    # Set the annotation label
    if radio == 0:
      sradio = 'Main'
      # Everything for the main radio
      self.b_radio_run .set_sensitive(False)
      self.b_vfoa.set_sensitive(True)
      self.b_vfob.set_sensitive(True)
      self.b_offseta.set_sensitive(True)
      self.b_offsetb.set_sensitive(True)
    else:
      sradio = 'R' + str(radio)
      # Nothing for sub-radios
      self.b_radio_run .set_sensitive(True)
      self.b_vfoa.set_sensitive(False)
      self.b_vfob.set_sensitive(False)
      self.b_offseta.set_sensitive(False)
      self.b_offsetb.set_sensitive(False)
      # If the radio is running already then make the run toggle button active  
      self.ignore = True
      if self.vr_data[radio]['running']:
        self.b_radio_run.set_active(True)
      else:
        self.b_radio_run.set_active(False)
      self.ignore = False
    
    # Set the annotation
    self.l_radio_controls.set_label('<span font_desc=\"Times 12 \" foreground=\"red\">'+ sradio +'</span>')
    
    # Re-get all radio specific data to our local cache in case we were listening elsewhere when changes occured
    track = utils.persist_get(self.persist, 'Dynamic', 'track')
    if self.which_radio == 0:
      # Default radio
      self.vr_data[0]['offseta'] = utils.persist_get(self.persist, 'Dynamic', 'offseta')
      self.vr_data[0]['modea'] = utils.persist_get(self.persist, 'Dynamic', 'modea')
      self.vr_data[0]['filtera'] = utils.persist_get(self.persist, 'Dynamic', 'filtera')
      self.vr_data[0]['offsetb'] = utils.persist_get(self.persist, 'Dynamic', 'offsetb')
      self.vr_data[0]['modeb'] = utils.persist_get(self.persist, 'Dynamic', 'modeb')
      self.vr_data[0]['filterb'] = utils.persist_get(self.persist, 'Dynamic', 'filterb')
      self.vr_data[0]['pan'] = utils.persist_get(self.persist, 'Dynamic', 'pan')
    else:
      # Sub-radio       
      self.vr_data[self.which_radio]['offseta'] = utils.persist_get(self.persist, 'Dynamic', 'offseta', self.which_radio)
      self.vr_data[self.which_radio]['offsetb'] = utils.persist_get(self.persist, 'Dynamic', 'offsetb', self.which_radio)
      self.vr_data[self.which_radio]['mode'] = utils.persist_get(self.persist, 'Dynamic', 'mode', self.which_radio)        
      self.vr_data[self.which_radio]['filter'] = utils.persist_get(self.persist, 'Dynamic', 'filter', self.which_radio)
      self.vr_data[self.which_radio]['pan'] = utils.persist_get(self.persist, 'Dynamic', 'pan', self.which_radio)
    
    # Set the dsp to listen for commands from this radio
    self.dsp_pub.setRxListen(self.which_radio)
    
    # Set the parameters for this radio or sub_radio
    # The active vfo is sent through the event system and will get reflected
    # The inactive vfo is set locally
    if self.which_radio == 0:
      if (track == 'vfoa') or (track == 'offseta'):
        self.dsp_pub.setMode(self.vr_data[0]['modea'])
        self.dsp_pub.setFilter(self.vr_data[0]['filtera'])
        if self.cap_oscillator:
          self.dsp_pub.oscSet(self.vr_data[0]['offseta'])
          self.osc_update('offsetb', self.vr_data[0]['offsetb'])
        self.mode_update('vfob', self.vr_data[0]['modeb'])
        self.filter_update('vfob', self.vr_data[0]['filterb'])
      else:
        self.dsp_pub.setMode(self.vr_data[0]['modeb'])
        self.dsp_pub.setFilter(self.vr_data[0]['filterb'])
        if self.cap_oscillator:
          self.dsp_pub.oscSet(self.vr_data[0]['offsetb'])
          self.osc_update('offseta', self.vr_data[0]['offseta'])
        self.mode_update('vfoa', self.vr_data[0]['modea'])
        self.filter_update('vfoa', self.vr_data[0]['filtera'])
    else:
      if (track == 'vfoa') or (track == 'offseta'):
        if self.cap_oscillator:
          self.dsp_pub.oscSet(self.vr_data[self.which_radio]['offseta'])
          self.osc_update('offsetb', self.vr_data[self.which_radio]['offsetb'])
      else:
        if self.cap_oscillator:
          self.dsp_pub.oscSet(self.vr_data[self.which_radio]['offsetb'])       
          self.osc_update('offseta', self.vr_data[self.which_radio]['offseta'])
      self.dsp_pub.setMode(self.vr_data[self.which_radio]['mode'])
      self.dsp_pub.setFilter(self.vr_data[self.which_radio]['filter'])
    if self.vr_data[self.which_radio]['pan'] <> 'Empty':
      self.dsp_pub.setRxPan(self.vr_data[self.which_radio]['pan'])
    
    # Get the vfo's etc in the correct state for the track
    # Don't send a track event as its only this radio that needs adjustment
    self.ignore = True
    if self.which_radio <> 0:
      # Not the main radio, so we must ensure the vfo is set up for oscillator only
      if track == 'vfoa': track = 'offseta'
      elif track == 'vfob': track = 'offsetb'
    self.adjust_vfo_for_track(track)
    self.ignore = False
  
  # The selected sub-radio state has been changed
  def run_radio_toggled(self, widget):
    if widget.get_active():
      if self.which_radio <> 0:
        # Not the default radio
        self.dsp_pub.setRxOn(self.which_radio)
        utils.persist_put(self.persist, 'Dynamic', 'running', True, self.which_radio)
    else:
      if self.which_radio <> 0:
        self.dsp_pub.setRxOff(self.which_radio)
        utils.persist_put(self.persist, 'Dynamic', 'running', False, self.which_radio)
        
  #---------------------------------------------------------------------
  def mode_controls(self, widget):
    if not self.ignore:
      self.c_modes.show_window()
  
  def mode_callback(self, mode):
    self.dsp_pub.setMode(mode)
    what = self.vr_data[0]['track']
    if (what == 'vfoa') or (what == 'offseta'):
      if self.which_radio == 0:
        utils.persist_put(self.persist, 'Dynamic', 'modea', mode)
      else:
        utils.persist_put(self.persist, 'Dynamic', 'mode', mode, self.which_radio)
    else:
      if self.which_radio == 0:
        utils.persist_put(self.persist, 'Dynamic', 'modeb', mode)
      else:
        utils.persist_put(self.persist, 'Dynamic', 'mode', mode, self.which_radio)
      
  #---------------------------------------------------------------------  
  def rx_filter_controls(self, widget):
    if not self.ignore:
      self.c_filters.show_window()
      
  def rx_filter_callback(self, filter):
    self.dsp_pub.setFilter(filter)
    what = self.vr_data[0]['track']
    if (what == 'vfoa') or (what == 'offseta'):
      if self.which_radio == 0:
        utils.persist_put(self.persist, 'Dynamic', 'filtera', filter)
      else:
        utils.persist_put(self.persist, 'Dynamic', 'filter', filter, self.which_radio)
    else:
      if self.which_radio == 0:
        utils.persist_put(self.persist, 'Dynamic', 'filterb', filter)
      else:
        utils.persist_put(self.persist, 'Dynamic', 'filter', filter, self.which_radio)
  
  #---------------------------------------------------------------------  
  def tx_filter_controls(self, widget):
    if not self.ignore:
      pass
  
  #--------------------------------------------------------------------- 
  def rx_proc_controls(self, widget):
    if not self.ignore:
      self.c_rx_proc.show_window()
      
  def rx_proc_callback(self, proc, state):
    if proc == 'bin':
      self.dsp_pub.setBin(state)
      utils.persist_put(self.persist, 'Dynamic', 'bin', state)
    elif proc == 'nb':
      self.dsp_pub.setNb(state)
      utils.persist_put(self.persist, 'Dynamic', 'nb', state)
    elif proc == 'nr':
      self.dsp_pub.setNr(state)
      utils.persist_put(self.persist, 'Dynamic', 'nr', state)
    elif proc == 'anf':
      self.dsp_pub.setAnf(state)
      utils.persist_put(self.persist, 'Dynamic', 'anf', state)
  
  #--------------------------------------------------------------------- 
  def tx_proc_controls(self, widget):
    if not self.ignore:
      pass
  
  #--------------------------------------------------------------------- 
  def agc_controls(self, widget):
    if not self.ignore:
      self.c_agc.show_window()
  
  def agc_callback(self, value):
    self.dsp_pub.setAgc(value)
    utils.persist_put(self.persist, 'Dynamic', 'agcmode', value)       

  #=====================================================================
  # Event reception
  #--------------------------------------------------------------------- 
  # Life cycle
  def thd_start(self):
    gobject.idle_add(self.start)
    
  def start(self):
    self.ignore = True
    self.mainbox.set_sensitive(True)
    self.brun.set_active(True)
    utils.persist_put(self.persist, 'Dynamic', 'running', True)
    self.ignore = False
  
  def thd_stop(self):
    gobject.idle_add(self.stop, data)
  
  def stop(self):
    self.ignore = True
    self.mainbox.set_sensitive(False)
    self.brun.set_active(False)
    utils.persist_put(self.persist, 'Dynamic', 'running', False)
    self.ignore = False
    
  def thd_terminate(self):
    gobject.idle_add(self.terminate, data)
  
  def terminate(self):
    gtk.main_quit()
    utils.persist_put(self.persist, 'Dynamic', 'running', False)
    
  #--------------------------------------------------------------------- 
  # Something on the AGC changed
  def thd_agc_update(self, data):
    gobject.idle_add(self.c_agc.agc_update, data)
    gobject.idle_add(self.agc_update, data)
    self.vr_data[0]['agc_mode'] = data
    
  def agc_update(self, data):
    map = {'AGC_FIXED': 'Fixed','AGC_LONG': 'Long','AGC_SLOW': 'Slow', 'AGC_MEDIUM': 'Medium','AGC_FAST': 'Fast'}
    self.l_agc_controls.set_label('<span font_desc=\"Times 12 \" foreground=\"red\">'+ map[data] +'</span>')
  
  #---------------------------------------------------------------------
  # VFO tracking changed
  def thd_track_update(self, data):
    self.vr_data[0]['track'] = data
    #print 'Track to ', data, ' A ', self.vr_data[0]['vfoa'], ' B ', self.vr_data[0]['vfob']
    gobject.idle_add(self.track_update, data)
  
  def track_update(self, data):
    # We track the VFO or Offset that is the target of adjustment. The main radio
    # is the only one allowed to change track. Sub-radios must follow the track but
    # cannot change it.
    
    # Isolate from circular events  
    self.ignore = True
    # The main radio responds to the 4 track changes (vfoa, vfob, offseta, offsetb)
    # However for the sub-radios we treat vfoa,b as offseta,b as we need to process
    # as if the offset had been selected. This is a transient processing state and
    # is not saved as we can always deduce it.
    if self.which_radio <> 0:
      # Not the main radio
      if data == 'vfoa': track = 'offseta'
      elif data == 'vfob': track = 'offsetb'
      else: track = data
    else: track = data

    # Get the VFO controls etc in the correct state for the track change
    self.adjust_vfo_for_track(track)
    
    # We can only initiate track commands from the main radio. This means that the
    # listening radio is always 0 as mouse over the main window sets listen to 0.
    # Therefore we only need to set the frequency, offset, mode and filter for the
    # appropriate track of the main radio.
    if self.which_radio == 0:
      if track == 'vfoa' or track == 'offseta':
        self.hw_pub.freqSet(self.vr_data[0]['vfoa'])
        self.dsp_pub.setMode(self.vr_data[0]['modea'])
        self.dsp_pub.setFilter(self.vr_data[0]['filtera'])
        if self.cap_oscillator: self.dsp_pub.oscSet(self.vr_data[self.which_radio]['offseta'])
      else:
        self.hw_pub.freqSet(self.vr_data[0]['vfob'])
        self.dsp_pub.setMode(self.vr_data[0]['modeb'])
        self.dsp_pub.setFilter(self.vr_data[0]['filterb'])
        if self.cap_oscillator: self.dsp_pub.oscSet(self.vr_data[self.which_radio]['offsetb'])
   
    self.ignore = False
   
  #---------------------------------------------------------------------
  # Change of virtual radios run or listen state (they may be started or stopped in anotherGUI)
  def thd_rx_on_update(self, data):
    gobject.idle_add(self.rx_on_update, data)
  
  def thd_rx_off_update(self, data):
    gobject.idle_add(self.rx_off_update, data)
  
  def rx_on_update(self, data):
    if data == 1:
      self.vr_data[1]['running'] = True
      self.l_vr_1.set_label('<span font_desc=\"Times 12\" foreground=\"red\">1</span>')
    elif data == 2:
      self.vr_data[2]['running'] = True
      self.l_vr_2.set_label('<span font_desc=\"Times 12\" foreground=\"red\">2</span>')
    else:
      self.vr_data[3]['running'] = True
      self.l_vr_3.set_label('<span font_desc=\"Times 12\" foreground=\"red\">3</span>')
      
  def rx_off_update(self, data):
    if data == 1:
      self.vr_data[1]['running'] = False
      self.l_vr_1.set_label('<span font_desc=\"Times 12\" foreground=\"grey\">1</span>')
    elif data == 2:
      self.vr_data[2]['running'] = False
      self.l_vr_2.set_label('<span font_desc=\"Times 12\" foreground=\"grey\">2</span>')
    else:
      self.vr_data[3]['running'] = False
      self.l_vr_3.set_label('<span font_desc=\"Times 12\" foreground=\"grey\">3</span>')
   
  def rx_listen_update(self, data):
    self.vr_data['listen'] = data
  
  #---------------------------------------------------------------------
  # Change of frequency
  def thd_freq_update(self, data):
    
    # Get the appropriate osc to add to the freq
    what = self.vr_data[0]['track']
    if self.cap_oscillator:
      if (what == 'vfoa') or (what == 'offseta'):
        osc = self.vr_data[self.which_radio]['offseta']
      else:
        osc = self.vr_data[self.which_radio]['offsetb']
    else:
      osc = None
    
    #print 'Changing freq for ', what, ' to ', data, osc
    gobject.idle_add(self.c_vfo.freq_update, data, osc)
    gobject.idle_add(self.freq_update, self.vr_data[0]['track'], data)
    # This is the exception, the best place to update the persistent state
    # Unfortunately all listening GUI's will set it
    #print 'Updating freq', self.which_radio, ' with ',self.vr_data[self.which_radio]['track'], data
    utils.persist_put(self.persist, 'Dynamic', self.vr_data[0]['track'], data)
  
  def freq_update(self, what, data, reset=False):
    if reset: colour = 'orange'
    else: colour = 'red'
    smhz, skhz, shz = utils.FreqToString(data)
    if (what == 'vfoa') or (what == 'offseta'):
      self.vr_data[self.which_radio]['vfoa'] = data
      self.l_vfoa.set_label('<span font_desc=\"Times 13\" foreground=\"'+colour+'\">A:</span><span font_desc=\"Times 13\" foreground=\"'+colour+'\">'+smhz+'.'+skhz+'.'+shz+'</span>')
    else:
      self.vr_data[self.which_radio]['vfob'] = data
      self.l_vfob.set_label('<span font_desc=\"Times 13\" foreground=\"'+colour+'\">B:</span><span font_desc=\"Times 13\" foreground=\"'+colour+'\">'+smhz+'.'+skhz+'.'+shz+'</span>')
    
  #---------------------------------------------------------------------
  # Change of oscillator
  def thd_osc_update(self, data):
    if self.vr_data['listen'] == self.which_radio:
      track = self.vr_data[0]['track']
      # We must always use the VFO that the main radio is using
      if track == 'vfoa' or track == 'offseta':
        freq = data + self.vr_data[0]['vfoa']
        target = 'offseta'
      else:
        freq = data + self.vr_data[0]['vfob']
        target = 'offsetb'
      gobject.idle_add(self.c_vfo.freq_update, freq)
      gobject.idle_add(self.osc_update, self.vr_data[0]['track'], data)
      # This is the exception, the best place to update the persistent state
      # Unfortunately all listening GUI's will set it
      #print 'Updating osc', self.which_radio, ' with ',target , data
      utils.persist_put(self.persist, 'Dynamic', target, data, self.which_radio)
  
  def osc_update(self, what, data, reset=False):
    if reset: colour = 'orange'
    else: colour = 'red'
    smhz, skhz, shz = utils.FreqToString(data)
    if data > 0.0: sign = '+'
    elif data < 0.0: sign = '-'
    else: sign = ' '
    if (what == 'vfoa') or (what == 'offseta'):
      self.vr_data[self.which_radio]['offseta'] = data
      self.l_offseta.set_label('<span font_desc=\"Times 13\" foreground=\"'+colour+'\">'+sign+'</span><span font_desc=\"Times 13\" foreground=\"'+colour+'\">'+skhz+'.'+shz+'</span>')
    else:
      self.vr_data[self.which_radio]['offsetb'] = data
      self.l_offsetb.set_label('<span font_desc=\"Times 13\" foreground=\"'+colour+'\">'+sign+'</span><span font_desc=\"Times 13\" foreground=\"'+colour+'\">'+skhz+'.'+shz+'</span>')
    
  #--------------------------------------------------------------------- 
  # Mode changed
  def thd_mode_update(self, data):
    if self.vr_data['listen'] == self.which_radio:
      gobject.idle_add(self.c_modes.mode_update, data)
      gobject.idle_add(self.mode_update, self.vr_data[0]['track'], data)
    
  def mode_update(self, what, data):
    map = {'LSB': 'LSB','USB': 'USB','DSB': 'DSB', 'CW-L': 'CW-L','CW-U': 'CW-U','FM-N': 'FM-N','AM': 'AM','SAM': 'SAM','DIGI-L': 'DIGI-L','DIGI-U': 'DIGI-U','SPEC': 'SPEC'}
    if self.which_radio == 0:
      # The default radio has modes for each vfo
      if (what == 'vfoa') or (what == 'offseta'):
        self.vr_data[0]['modea'] = data
        self.l_mode_controls.set_label('<span font_desc=\"Times 12 \" foreground=\"red\">A:'+ map[data] +'</span><span font_desc=\"Times 12 \" foreground=\"orange\">, B:'+ map[self.vr_data[0]['modeb']] +'</span>')
      else:
        self.vr_data[0]['modeb'] = data
        self.l_mode_controls.set_label('<span font_desc=\"Times 12 \" foreground=\"orange\">A:'+ map[self.vr_data[0]['modea']] +'</span><span font_desc=\"Times 12 \" foreground=\"red\">, B:'+ map[data] +'</span>')
    else:
      # One of the VR's
      self.vr_data[self.which_radio]['mode'] = data
      self.l_mode_controls.set_label('<span font_desc=\"Times 12 \" foreground=\"red\">'+ str(self.which_radio) + ': ' + map[data] +'</span>')
      
  #---------------------------------------------------------------------  
  # Filter changed
  def thd_filter_update(self, data):
    if self.vr_data['listen'] == self.which_radio:
      gobject.idle_add(self.c_filters.filter_update, data)
      gobject.idle_add(self.filter_update, self.vr_data[0]['track'], data)
    
  def filter_update(self, what, data):
    map = {'6K0': '6.0','2K4': '2.4','2K1': '2.1','1K0': '1.0','500': '500','250': '250','100': '100','50': '50', }
    if self.which_radio == 0:
      # The default radio has filters for each vfo
      if (what == 'vfoa') or (what == 'offseta'):
        self.vr_data[0]['filtera'] = data
        self.l_rx_filter_controls.set_label('<span font_desc=\"Times 12 \" foreground=\"red\">A:'+ map[data] +'</span><span font_desc=\"Times 12 \" foreground=\"orange\">, B:'+ map[self.vr_data[0]['filterb']] +'</span>')
      else:
        self.vr_data[0]['filterb'] = data
        self.l_rx_filter_controls.set_label('<span font_desc=\"Times 12 \" foreground=\"orange\">A:'+ map[self.vr_data[0]['filtera']] +'</span><span font_desc=\"Times 12 \" foreground=\"red\">, B:'+ map[data] +'</span>')
    else:
      # One of the VR's
      self.vr_data[self.which_radio]['filter'] = data
      self.l_rx_filter_controls.set_label('<span font_desc=\"Times 12 \" foreground=\"red\">'+ str(self.which_radio) + ': ' + map[data] +'</span>')
      
  #---------------------------------------------------------------------  
  # RX Processors changed
  def thd_bin_update(self, data):
    gobject.idle_add(self.c_rx_proc.bin_update, data)
    gobject.idle_add(self.rx_proc_update, data)
    self.vr_data[0]['bin'] = data
    
  def thd_nr_update(self, data):
    gobject.idle_add(self.c_rx_proc.nr_update, data)
    gobject.idle_add(self.rx_proc_update, data)
    self.vr_data[0]['nr'] = data
    
  def thd_nb_update(self, data):
    gobject.idle_add(self.c_rx_proc.nb_update, data)
    gobject.idle_add(self.rx_proc_update, data)
    self.vr_data[0]['nb'] = data
    
  def thd_anf_update(self, data):
    gobject.idle_add(self.c_rx_proc.anf_update, data)
    gobject.idle_add(self.rx_proc_update, data)
    self.vr_data[0]['anf'] = data
    
  def rx_proc_update(self, data):
    procs = ''
    first = True
    if 'bin' in self.vr_data[0]:
      if self.vr_data[0]['bin']:
        if not first:
          procs = procs + ', '
        first = False
        procs = 'BIN'
    if 'nr' in self.vr_data[0]:
      if self.vr_data[0]['nr']:
        if not first:
          procs = procs + ', '
        first = False
        procs = procs + 'NR'
    if 'nb' in self.vr_data[0]:
      if self.vr_data[0]['nb']:
        if not first:
          procs = procs + ', '
        first = False
        procs = procs + 'NB'
    if 'anf' in self.vr_data[0]:
      if self.vr_data[0]['anf']:
        if not first:
          procs = procs + ', '
        first = False
        procs = procs + 'ANF'
    
    self.l_rx_proc_controls.set_label('<span font_desc=\"Times 12 \" foreground=\"red\">'+ procs +'</span>')

  #---------------------------------------------------------------------  
  # Gain changed
  def thd_gain_update(self, data):
    gobject.idle_add(self.gain_update, data)
    
  def gain_update(self, data):
    self.ignore = True
    self.vr_data[0]['gain'] = data
    self.af_gain.set_value(float(data))
    self.ignore = False
    
  #---------------------------------------------------------------------  
  # Pan changed
  def thd_pan_update(self, data):
    gobject.idle_add(self.pan_update, data)
    
  def pan_update(self, data):
    if self.vr_data['listen'] == self.which_radio:
      # We are set to the same radio the command is for
      self.ignore = True
      self.vr_data[self.which_radio]['pan'] = data
      self.pan.set_value(float(data))
      self.ignore = False
    
  #=====================================================================
  # PRIVATE
  #---------------------------------------------------------------------  
  def adjust_vfo_for_track(self, track):
    # We are going to reset the state of the interactors to correspond to the track change.
    # Set everything off to start with and then selectively enable and set attributes.
    # We set each non-selected and orange as the non-selected visual indicator.
    # Then set the appropriate one selected and change its colour to red.
    # Note that some widgets are not sensitive (sub_radios) and therefore the colour
    # is somewhat pointless but the selected state does show with a select rectangle
    self.b_vfoa.set_active(False)
    self.l_vfoa.set_label(self.l_vfoa.get_label().replace('red', 'orange'))
    self.b_vfob.set_active(False)
    self.l_vfob.set_label(self.l_vfob.get_label().replace('red', 'orange'))
    self.b_offseta.set_active(False)
    self.l_offseta.set_label(self.l_offseta.get_label().replace('red', 'orange'))
    self.b_offsetb.set_active(False)
    self.l_offsetb.set_label(self.l_offsetb.get_label().replace('red', 'orange'))
    if track == 'vfoa':
      # Can only be tracked for the main radio
      self.b_vfoa.set_active(True)
      self.l_vfoa.set_label(self.l_vfoa.get_label().replace('orange', 'red'))
    elif track == 'vfob':
      # Can only be tracked for the main radio
      self.b_vfob.set_active(True)
      self.l_vfob.set_label(self.l_vfob.get_label().replace('orange', 'red'))
    elif track == 'offseta':
      self.b_offseta.set_active(True)
      self.l_offseta.set_label(self.l_offseta.get_label().replace('orange', 'red'))
    elif track == 'offsetb':
      self.b_offsetb.set_active(True)
      self.l_offsetb.set_label(self.l_offsetb.get_label().replace('orange', 'red'))
      
    # The vfo is modified for true VFO frequency and Offset frequency
    # This involves changing the colour of the digits that cannot be scrolled
    # and setting the publisher to send update commands to (the controller for frequency
    # and dsp for offsets)
    if track == 'vfoa':
      self.c_vfo.set_publisher(self.hw_pub.freqInc)
      self.c_vfo.set_colour('red', 'red', 'red')
      self.c_vfo.set_mode('vfoa')
    elif track == 'vfob':
      self.c_vfo.set_publisher(self.hw_pub.freqInc)
      self.c_vfo.set_colour('red', 'red', 'red')
      self.c_vfo.set_mode('vfob')
    elif track == 'offseta':
      self.c_vfo.set_publisher(self.dsp_pub.oscInc)
      self.c_vfo.set_colour('orange', 'red', 'red')
      self.c_vfo.set_mode('offseta')
    elif track == 'offsetb':
      self.c_vfo.set_publisher(self.dsp_pub.oscInc)
      self.c_vfo.set_colour('orange', 'red', 'red')
      self.c_vfo.set_mode('offsetb')
        
  #=====================================================================
  # Entry point
  def main(self):
    # All PyGTK applications must have a gtk.main(). Control ends here
    # and waits for an event to occur (like a key press or mouse event)
    gtk.main()
    # Return here when the UI is closed
    return self.quitall
